﻿using KinonekoSoftware.CodariaDB.Runtimes.IO;

namespace KinonekoSoftware.CodariaDB.Runtimes
{
    public struct RepositoryLoadContext
    {
        public DataMode     Mode             { get; init; }
        public string       Workspace        { get; init; }
        public LiteDatabase Database         { get; init; }
        public string       IndexerFileName  { get; init; }
        public string       DatabaseFileName { get; init; }
        public string       BackupFileName   { get; init; }
    }

    public class Repository : ValuePool, IRepository
    {
        public const string Repository_ID          = "r_id";
        public const string Repository_Name        = "r_name";
        public const string Repository_ForeignName = "r_fname";
        public const string Repository_Authors     = "r_author";
        public const string Repository_Intro       = "r_intro";
        public const string Repository_Avatar      = "r_avatar";

        private readonly DataMode                  _mode;
        private readonly RepositoryPropertyManager _rpMgr;

        private bool   _initialized;
        private string _name;
        private string _avatar;
        private string _author;
        private string _foreignName;
        private string _intro;

        public Repository(RepositoryLoadContext context) : base(context.Database)
        {
            _rpMgr           = new RepositoryPropertyManager(this);
            _mode            = context.Mode;
            Kernel           = context.Database ?? throw new ArgumentNullException(nameof(RepositoryLoadContext.Database));
            IndexerFileName  = context.IndexerFileName;
            DatabaseFileName = context.DatabaseFileName;
            BackupFileName   = context.BackupFileName;
            Workspace        = context.Workspace;

            if (string.IsNullOrEmpty(Id))
            {
                UpdateProperty();
            }
        }

        internal void Initialize()
        {
            _name        = Strings[Repository_Name];
            _foreignName = Strings[Repository_ForeignName];
            _avatar      = Strings[Repository_Avatar];
            _author      = Strings[Repository_Authors];
            _intro       = Strings[Repository_Intro];
            _initialized = true;
        }

        public void SetIf<T>(Factory<T> factory) where T : EmbeddedObject
        {
            if (!Has<T>())
            {
                Set(factory(), true);
            }
        }


        #region Create

        public static Repository Create(out MemoryStream ms, out MemoryStream log)
        {
            ms  = new MemoryStream();
            log = new MemoryStream();
            return new Repository(new RepositoryLoadContext
            {

                Mode             = DataMode.Protected,
                Workspace        = "C:\\",
                IndexerFileName  = "C:\\t.c",
                DatabaseFileName = "C:\\t.p",
                BackupFileName   = "C:\\t.backup.p",
                Database         = new LiteDatabase(ms, DatabaseBsonMapper.Override, log),
            });
        }

        #endregion

        #region Disposable

        protected override void ReleaseUnmanagedResources()
        {
            Kernel.Dispose();
        }

        protected override void ReleaseManagedResources()
        {
            //
            //
            base.ReleaseManagedResources();
        }

        #endregion

        /// <summary>
        /// 创建快照
        /// </summary>
        /// <returns>返回当前数据库的快照。</returns>
        public RepositoryCache Snapshot()
        {
            return new RepositoryCache
            {
                Id          = Id,
                Name        = Name,
                ForeignName = ForeignName,
                Author      = Author,
                Intro       = Intro,
                Path        = Workspace,
            };
        }

        public void DumpAsync()
        {
        }


        #region Collection Op Methods

        private HashSet<string> _hashSet;
        private bool            _dirty;


        /// <summary>
        /// 创建一个数据库集合
        /// </summary>
        /// <param name="collectionName">集合名</param>
        /// <typeparam name="T">数据类型</typeparam>
        /// <returns>返回一个数据库集合</returns>
        internal ILiteCollection<T> GetCollection<T>(string collectionName) where T : class
        {
            // if (Has(collectionName))
            // {
            //     return Kernel.GetCollection<T>(collectionName);
            // }

            //
            // in the pass we use Initializing handler to resolve initializing phase
            // Initializing?.Invoke(this, collectionName);
            //
            //
            var col = Kernel.GetCollection<T>(collectionName);
            _hashSet ??= new HashSet<string>();
            _hashSet.Add(collectionName);
            _dirty = true;
            return col;
        }

        /// <summary>
        /// 创建一个数据库集合
        /// </summary>
        /// <param name="collectionName">集合名</param>
        /// <typeparam name="T">数据类型</typeparam>
        /// <returns>返回一个数据库集合</returns>
        public ILiteCollection<T> Get<T>(string collectionName) where T : class
        {
            // if (Has(collectionName))
            // {
            //     return Kernel.GetCollection<T>(collectionName);
            // }

            //
            // in the pass we use Initializing handler to resolve initializing phase
            // Initializing?.Invoke(this, collectionName);
            //
            //
            var col = Kernel.GetCollection<T>(collectionName);
            _hashSet ??= new HashSet<string>();
            _hashSet.Add(collectionName);
            _dirty = true;
            return col;
        }

        /// <summary>
        /// 获取一个值，这个值用于表示数据库中是否有指定集合名的集合存在。
        /// </summary>
        /// <param name="collectionName">集合名</param>
        /// <returns>返回一个值，这个值用于表示数据库中是否有指定集合名的集合存在。如果为true则存在，否则不存在。</returns>
        public bool Has(string collectionName)
        {
            if (_hashSet is null || _dirty)
            {
                var col = Kernel.GetCollection("$cols");
                _hashSet = col.FindAll()
                              .Select(x => x["name"].AsString)
                              .ToHashSet();
            }

            return !string.IsNullOrEmpty(collectionName) && _hashSet.Contains(collectionName);
        }

        /// <summary>
        /// 删除一个数据库集合
        /// </summary>
        /// <param name="collectionName">集合名</param>
        /// <returns>返回操作结果</returns>
        public bool Drop(string collectionName)
        {
            if (string.IsNullOrEmpty(collectionName))
            {
                return false;
            }

            if (Kernel.DropCollection(collectionName))
            {

                _dirty = true;
                return true;
            }

            return false;
        }

        /// <summary>
        /// 开始一个事务
        /// </summary>
        /// <returns>返回操作结果</returns>
        public bool BeginTransaction() => Kernel.BeginTrans();

        /// <summary>
        /// 结束一个事务
        /// </summary>
        /// <returns>返回操作结果</returns>
        public bool EndTransaction() => Kernel.Commit();

        #endregion

        #region Properties

        public int DatabaseVersion
        {
            get => Integers[nameof(DatabaseVersion)];
            set
            {
                Integers[nameof(DatabaseVersion)] = value;
            }
        }

        public void UpdateProperty()
        {
            Id = string.IsNullOrEmpty(Id) ? ID.GetShortID() : Id;
            Strings[Repository_ID]          = Id;
            Strings[Repository_Name]        = Name;
            Strings[Repository_ForeignName] = ForeignName;
            Strings[Repository_Avatar]      = Avatar;
            Strings[Repository_Authors]     = Author;
            Strings[Repository_Intro]       = Intro;

        }

        public string Id { get; internal set; }

        public string Name
        {
            get
            {
                if (!_initialized)
                {
                    Initialize();
                }
                return _name;
            }
            set
            {
                _name = value;
                Modified();
            }
        }

        public string ForeignName
        {
            get
            {
                if (!_initialized)
                {
                    Initialize();
                }
                return _foreignName;
            }
            set
            {
                _foreignName = value;
                Modified();
            }
        }

        public string Avatar
        {
            get
            {
                if (!_initialized)
                {
                    Initialize();
                }
                return _avatar;
            }
            set
            {
                _avatar = value;
                Modified();
            }
        }

        public string Author
        {
            get
            {
                if (!_initialized)
                {
                    Initialize();
                }
                return _author;
            }
            set
            {
                _author = value;
                Modified();
            }
        }

        public string Intro
        {
            get
            {
                if (!_initialized)
                {
                    Initialize();
                }
                return _intro;
            }
            set
            {
                _intro = value;
                Modified();
            }
        }

        #endregion

        private const string   SQL_FreeByte   = "select SUM(*.freeBytes) from $dump";
        private const string   SQL_UsageBytes = "select SUM(*.usedBytes) from $dump";
        public        DataMode Mode => _mode;

        public LiteDatabase Kernel           { get; }
        public string       IndexerFileName  { get; }
        public string       DatabaseFileName { get; }
        public string       BackupFileName   { get; }
        public string       Workspace        { get; }
        public string       DatabaseID       => Id;
        public ILogger      Logger           { get; internal set; }

        public long UsageBytes
        {
            get
            {
                return Kernel.Execute(SQL_UsageBytes)
                             .Single()["usedBytes"]
                             .AsInt64;
            }
        }

        public long FreeBytes
        {
            get
            {
                // return Kernel.Execute("select SUM(*.freeBytes) from $dump where pageType = 'Data' and collection = 'collection'")
                //              .Single()["usedBytes"]
                //              .AsInt64;
                return Kernel.Execute(SQL_FreeByte)
                             .Single()["freeBytes"]
                             .AsInt64;
            }
        }

        public RepositoryPropertyManager PropertyManager => _rpMgr;
    }
}